﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using System.Security.Cryptography;


namespace Client.Cache
{
    abstract class CacheObject
    {

        protected string _path;

        private const string FILE_EXTENSION = ".dat";

        private const string LIST_FILE = "files.xml";

        protected Dictionary<string, DateTime?> _storedFiles;

        public int StoredFilesCount
        {
            get
            {
                return _storedFiles.Count;
            }
        }

        public long StoredSize
        {
            get
            {
                long total = 0;
                DirectoryInfo dir = new DirectoryInfo(this._path);
                foreach (System.IO.FileInfo file in dir.GetFiles())
                {
                    total += file.Length;
                }

                return total;
            }
        }

        public CacheObject()
            : this(@"Datastore\Cache")
        {

        }

        public CacheObject(string folder)
        {
            this._path = Path.Combine(Environment.CurrentDirectory, folder);
            this.ReadList();            
        }

        ~CacheObject()
        {
            this.SaveList();
        }

        private void ReadList()
        {
            if (File.Exists(Path.Combine(_path, LIST_FILE)))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(List<FileInfo>));
                using (Stream file = File.OpenRead(Path.Combine(_path, LIST_FILE)))
                {
                    
                    var list = (List<FileInfo>)serializer.Deserialize(file);
                    this._storedFiles = list.ToDictionary(l => l.Key, l => l.ExpiryDate);
                }
            } else{
                this._storedFiles = new Dictionary<string, DateTime?>();
            }
        }

        private void SaveList()
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<FileInfo>));
            using (Stream s = File.Create(Path.Combine(_path, LIST_FILE)))
            {
                List<FileInfo> list = this._storedFiles
                    .Select(f => new FileInfo { Key = f.Key, ExpiryDate = f.Value })
                    .ToList();
                serializer.Serialize(s, list);
            }
        }

        private bool Exist(string hash)
        {
            if(_storedFiles.Any( f => f.Key == hash && (f.Value == null || f.Value > DateTime.Now))){
                if (File.Exists(Path.Combine(_path, hash + FILE_EXTENSION)))
                {
                    return true;
                } else {
                    _storedFiles.Remove(hash);
                    return false;
                }
            } else {
                return false;
            }
        }

        private string Hash(string key)
        {
            MD5CryptoServiceProvider MD5 = new MD5CryptoServiceProvider();
            byte[] byteArray = Encoding.UTF8.GetBytes(key);
            byteArray = MD5.ComputeHash(byteArray);
            StringBuilder s = new StringBuilder();
            foreach (byte b in byteArray)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            return s.ToString();
        }

        protected bool Try(string key)
        {
            
            return this.Exist(this.Hash(key));
        }

        protected byte[] Get(string key)
        {
            string hash = this.Hash(key);
            if (_storedFiles.Any(f => f.Key == hash && (f.Value == null || f.Value < DateTime.Now)))
            {
                string path = Path.Combine(_path, hash + FILE_EXTENSION);
                return File.ReadAllBytes(path);
            }

            throw new Exception("Brak wartości w cache");
        }

        protected void Set(string key, byte[] value, DateTime? expiryDate = null)
        {
            string hash = this.Hash(key);
            string path = Path.Combine(_path, hash + FILE_EXTENSION);
            File.WriteAllBytes(path, value);
            _storedFiles[hash] = expiryDate;
            this.Clear();
        }

        protected void Remove(string key)
        {
            string hash = this.Hash(key);
            File.Delete(Path.Combine(_path, hash + FILE_EXTENSION));
            _storedFiles.Remove(hash);
        }

        public void RemoveAll()
        {
            foreach (var file in _storedFiles)
            {
                File.Delete(Path.Combine(_path, file.Key + FILE_EXTENSION));
            }

            _storedFiles = new Dictionary<string, DateTime?>();
        }

        public void Clear()
        {
            var filesToRemove = _storedFiles.Where(f => f.Value != null && f.Value > DateTime.Now).ToList();

            foreach (var file in filesToRemove)
            {
                File.Delete(Path.Combine(_path, file.Key + FILE_EXTENSION));
                _storedFiles.Remove(file.Key);
            }
            this.SaveList();
        }
    }
}
